package com.xinsoft.controller.erp;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.api.R;
import com.xinsoft.common.Response;
import com.xinsoft.common.exception.BDException;
import com.xinsoft.constant.Const;
import com.xinsoft.entity.dto.*;
import com.xinsoft.entity.po.*;
import com.xinsoft.service.*;
import com.xinsoft.service.impl.SyncPushBillAdapter;
import com.xinsoft.utils.TokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.Synchronized;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 业务单据推送
 * @folder erp同步/业务单据推送
 */
@Slf4j
@Validated
@RestController
@RequestMapping("/erp/pushBill")
public class ErpPushBillController {

    @Resource(name = "syncPushBillAdapter")
    private SyncPushBillAdapter syncPushBillAdapter;
    @Autowired
    private SysDictionaryService dictionaryService;
    @Autowired
    private QuaMaterialInspectionService quaMaterialInspectionService;
    @Autowired
    private SysDocumentNumService sysDocumentNumService;
    @Autowired
    private QuaCheckPlanService quaCheckPlanService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private QuaCheckItemService quaCheckItemService;
    @Autowired
    private QuaPlanCheckRefService quaPlanCheckRefService;
    @Autowired
    private SysMaterialService materialService;
    @Autowired
    private ScmStockService stockService;


    /**
     * 生产领料
     */
    @PostMapping("/feedingOutStock")
    public Response<?> feedingOutStock(@Valid @RequestBody List<ErpInOrOutStockParam> params) {
        return syncPushBillAdapter.feedingOutStock(params);
    }

    /**
     * 生产退料
     */
    @PostMapping("/feedingInStock")
    public Response<?> feedingInStock(@Valid @RequestBody List<ErpInOrOutStockParam> params) {
        return syncPushBillAdapter.feedingInStock(params);
    }

    /**
     * 委外出库
     */
    @PostMapping("/sourcingFeedingOutStock")
    public Response<?> sourcingFeedingOutStock(@Valid @RequestBody List<ErpInOrOutStockParam> params) {
        return syncPushBillAdapter.sourcingFeedingOutStock(params);
    }

    /**
     * 委外入库
     */
    @PostMapping("/sourcingFeedingInStock")
    public Response<?> sourcingFeedingInStock(@Valid @RequestBody List<ErpInOrOutStockParam> params) {
        return syncPushBillAdapter.sourcingFeedingInStock(params);
    }

    /**
     * 生产入库
     */
    @PostMapping("/produceInStock")
    @Transactional(rollbackFor = Exception.class)
    public Response<?> produceInStock(@Validated @RequestBody List<ErpInOrOutStockParam> params) {
        // 保存生产入库信息，用于超过180天未出库提醒
        Map<String, ScmStock> stockMap = this.getStockMap(params);
        List<ScmStock> scmStockList = new ArrayList<>();
        params.forEach(item -> {
            String key = String.join("_", item.getMaterialId().toString(), item.getBatch(), item.getWarehouseId().toString(), item.getWarehItemId().toString());
            ScmStock stock = null;
            if (ObjectUtil.isNotEmpty(stockMap) && stockMap.containsKey(key)) {
                stock = stockMap.get(key);
                stock.setBatchNumFree(stock.getBatchNumFree().add(item.getQty()));
            } else {
                stock = new ScmStock();
                stock.setMaterialId(item.getMaterialId());
                stock.setBatchNo(item.getBatch());
                stock.setBatchNumFree(item.getQty());
                stock.setStockType(Const.DOCUMENT_ID.PRODUCE_INSTOCK_DOCUMENT);
                stock.setWarehouseId(item.getWarehouseId());
                stock.setWarehItemId(item.getWarehItemId());
            }
            scmStockList.add(stock);
        });
        stockService.saveOrUpdateBatch(scmStockList);
        // 推送生产入库给金蝶
        return syncPushBillAdapter.produceInStock(params);
    }

    /**
     * 获取库存记录
     * @param params
     * @return {@link Map}<{@link String}, {@link ScmStock}>
     */
    public Map<String, ScmStock> getStockMap(List<ErpInOrOutStockParam> params){
        List<Integer> materialIds = params.stream().map(ErpInOrOutStockParam::getMaterialId).distinct().collect(Collectors.toList());
        List<String> batchs = params.stream().map(ErpInOrOutStockParam::getBatch).distinct().collect(Collectors.toList());
        List<Integer> warehouseIds = params.stream().map(ErpInOrOutStockParam::getWarehouseId).distinct().collect(Collectors.toList());
        List<Integer> warehouseItemIds = params.stream().map(ErpInOrOutStockParam::getWarehItemId).distinct().collect(Collectors.toList());
        List<ScmStock> stockList = stockService.list(Wrappers.lambdaQuery(ScmStock.class)
                .in(ScmStock::getMaterialId, materialIds)
                .in(ScmStock::getBatchNo, batchs)
                .in(ScmStock::getWarehouseId, warehouseIds)
                .in(ScmStock::getWarehItemId, warehouseItemIds));
        if (CollectionUtil.isNotEmpty(stockList)){
            return stockList.stream().collect(Collectors.toMap(s -> String.join("_", s.getMaterialId().toString(), s.getBatchNo(), s.getWarehouseId().toString(), s.getWarehItemId().toString()), s -> s));
        }
        return null;
    }

    /**
     * 采购入库
     */
    @PostMapping("/purchaseInStock")
    public Response<?> purchaseInStock(@Valid @RequestBody List<ErpInOrOutStockParam> params) {
        return syncPushBillAdapter.purchaseInStock(params);
    }

    /**
     * 采购退货
     */
    @PostMapping("/purchaseOutStock")
    public Response<?> purchaseOutStock(@Valid @RequestBody List<ErpInOrOutStockParam> params) {
        return syncPushBillAdapter.purchaseOutStock(params);
    }

    /**
     * 其他入库
     */
    @PostMapping("/otherInStock")
    public Response<?> otherInStock(@Valid @RequestBody List<ErpInOrOutStockParam> params) {
        return syncPushBillAdapter.otherInStock(params);
    }

    /**
     * 其他出库
     */
    @PostMapping("/otherOutStock")
    public Response<?> otherOutStock(@Valid @RequestBody List<ErpInOrOutStockParam> params) {
        return syncPushBillAdapter.otherOutStock(params);
    }


    /**
     * 销售出库
     */
    @PostMapping("/saleOutStock")
    @Transactional(rollbackFor = Exception.class)
    public Response<?> saleOutStock(@Valid @RequestBody List<ErpInOrOutStockParam> params) {
        // TODO 改造完成后将废弃
        log.info("销售出库传参：{}", JSON.toJSONString(params));
        Map<String, ScmStock> stockMap = this.getStockMap(params);
        if (Objects.nonNull(stockMap)){
            // 非null为生产入库的产品需更新可用数量，用于消息提醒长时间未使用
            List<ScmStock> scmStockList = new ArrayList<>();
            params.forEach(item -> {
                String key = String.join("_", item.getMaterialId().toString(), item.getBatch(), item.getWarehouseId().toString(), item.getWarehItemId().toString());
                ScmStock stock = null;
                if (stockMap.containsKey(key)) {
                    stock = stockMap.get(key);
                    stock.setBatchNumFree(stock.getBatchNumFree().subtract(item.getQty()));
                    scmStockList.add(stock);
                }
            });
            stockService.updateBatchById(scmStockList);
        }
        // 推送销售给金蝶
        return syncPushBillAdapter.saleOutStock(params);
    }

    /**
     * 库存调拨
     */
    @PostMapping("/inventoryTransfer")
    public Response<?> inventoryTransfer(@Valid @RequestBody List<ErpInOrOutStockParam> params) {
        log.info("调拨传参：{}", params);
        long count = params.stream().filter(p -> Objects.isNull(p.getMoveInWarehouseId()) || Objects.isNull(p.getMoveInWarehouseItemId())).count();
        if (count > 0) {
            throw new BDException("调入仓库或调入仓位不能为空");
        }
        return syncPushBillAdapter.inventoryTransfer(params);
    }

    /**
     * 库存盘点
     */
    @PostMapping("/inventoryVerification")
    public Response<?> inventoryVerification(@Valid @RequestBody List<ErpInOrOutStockParam> params) {
        long count = params.stream().filter(p -> Objects.isNull(p.getInvQty()) || Objects.isNull(p.getQty())).count();
        if (count > 0) {
            throw new BDException("系统数量或盘点数量不能为空");
        }
        List<ErpInOrOutStockParam> profitList = params.stream().filter(p -> p.getQty().compareTo(p.getInvQty()) > 0).collect(Collectors.toList());
        List<ErpInOrOutStockParam> lossList = params.stream().filter(p -> p.getQty().compareTo(p.getInvQty()) < 0).collect(Collectors.toList());
        if (CollectionUtil.isNotEmpty(profitList)) {
            syncPushBillAdapter.inventoryProfit(profitList);
        }
        if (CollectionUtil.isNotEmpty(lossList)) {
            syncPushBillAdapter.inventoryLoss(lossList);
        }
        return Response.succeed();
    }

    /**
     * 采购收料通知
     */
    @PostMapping("/purchaseNotice")
    @Transactional(rollbackFor = Exception.class)
    public Response<?> purchaseNotice(@Valid @RequestBody List<ErpNoticeBillParam> params) {
        Boolean aBoolean = this.limitFrequentCalls("采购收料通知", null);
        if (aBoolean) {
            return Response.fail("请勿频繁操作！");
        }
        // 组装检验数据
        QuaMaterialInspectionDTO dto = this.assembleQuaMaterialInspection(params);
        // 推送采购收料通知给金蝶
        SysDictionary dict = dictionaryService.getById(10792);
        params.forEach(param -> {
            param.setNoticeTypeId("71");
            param.setWarehouseId(Integer.valueOf(dict.getDicValue()));
        });
        KisApiResultVOS kisApiResultVOS = syncPushBillAdapter.purchaseNotice(params);
        log.info("MES->KIS推送采购收料通知，kisApiResultVOS = {}", kisApiResultVOS);
        if (ObjectUtil.isNotEmpty(kisApiResultVOS.getStatus()) && kisApiResultVOS.getStatus().equals(0)) {
            if (kisApiResultVOS.getMsg().contains("所选单据已经关闭")) {
                throw new BDException("所选单据已经关闭");
            }
        }
        // 推送来料检验
        List<JSONObject> jsonObjectList = JSONUtil.toList(kisApiResultVOS.getMsg(), JSONObject.class);
        String billNo = jsonObjectList.get(0).get("BillNo").toString();
        String interId = jsonObjectList.get(0).get("InterId").toString();
        dto.setSourceId(Integer.valueOf(interId));
        dto.setSourceNo(billNo);
        dto.setIncomingMaterialType(Const.IncomingMaterialType.PURCHASE_FROM_OUTSIDE);
        quaMaterialInspectionService.addQuaMaterialInspection(dto);
        return Response.succeed();
    }

    /**
     * 委外收料通知
     */
    @PostMapping("/sourcingNotice")
    @Transactional(rollbackFor = Exception.class)
    public Response<?> sourcingNotice(@Valid @RequestBody List<ErpNoticeBillParam> params) {
        Boolean aBoolean = this.limitFrequentCalls("委外收料通知", null);
        if (aBoolean) {
            return Response.fail("请勿频繁操作！");
        }
        // 组装检验数据
        QuaMaterialInspectionDTO dto = this.assembleQuaMaterialInspection(params);
        // 推送委外收料通知给金蝶
        SysDictionary dict = dictionaryService.getById(10792);
        params.forEach(param -> {
            param.setNoticeTypeId("1007105");
            param.setWarehouseId(Integer.valueOf(dict.getDicValue()));
        });
        KisApiResultVOS kisApiResultVOS = syncPushBillAdapter.sourcingNotice(params);
        log.info("MES->KIS推送委外收料通知，kisApiResultVOS = {}", kisApiResultVOS);
        if (ObjectUtil.isNotEmpty(kisApiResultVOS.getStatus()) && kisApiResultVOS.getStatus().equals(0)) {
            if (kisApiResultVOS.getMsg().contains("所选单据已经关闭")) {
                throw new BDException("所选单据已经关闭");
            }
        }
        // 推送来料检验
        List<JSONObject> jsonObjectList = JSONUtil.toList(kisApiResultVOS.getMsg(), JSONObject.class);
        String billNo = jsonObjectList.get(0).get("BillNo").toString();
        String interId = jsonObjectList.get(0).get("InterId").toString();
        dto.setSourceId(Integer.valueOf(interId));
        dto.setSourceNo(billNo);
        dto.setIncomingMaterialType(Const.IncomingMaterialType.BUSINESS_PROCESS_OUTSOURCING);
        quaMaterialInspectionService.addQuaMaterialInspection(dto);
        return Response.succeed();
    }

    /**
     * 组装来料检验的推送数据
     * @param params
     */
    @Synchronized
    public QuaMaterialInspectionDTO assembleQuaMaterialInspection(List<ErpNoticeBillParam> params) {
        ErpNoticeBillParam billParam = params.get(0); // 实际只会有一条
        QuaMaterialInspectionDTO dto = new QuaMaterialInspectionDTO();
        Integer documentId = Const.DOCUMENT_ID.QUA_LLJY_DOCUMENT;
        String inspectionNo = sysDocumentNumService.getSysDocumentNumByDocumentId(documentId);
        dto.setInspectionType(Const.QUA_TYPE.QUA_LLJY);
        dto.setMaterialId(billParam.getMaterialId());
        dto.setBatchNo(billParam.getBatch());
        dto.setSendCount(billParam.getQty());
        dto.setInspectionNo(inspectionNo);
        sysDocumentNumService.incrementLastSerialByDocumentId(documentId);

        List<QuaCheckPlan> checkPlanList = quaCheckPlanService.list(Wrappers.lambdaQuery(QuaCheckPlan.class)
                .eq(QuaCheckPlan::getCheckTypeId, Const.QUA_TYPE.QUA_LLJY)
                .like(QuaCheckPlan::getCheckMaterialIds, billParam.getMaterialId()));
        if (CollectionUtil.isEmpty(checkPlanList)) {
            SysMaterial material = materialService.getById(billParam.getMaterialId());
            throw new BDException(String.format("物料%s没有配置检验方案", material.getMaterialName()));
        }
        List<Integer> checkPlanIds = checkPlanList.stream().map(QuaCheckPlan::getId).distinct().collect(Collectors.toList());
        List<QuaPlanCheckRef> quaPlanCheckRefs = quaPlanCheckRefService.list(Wrappers.lambdaQuery(QuaPlanCheckRef.class)
                .in(QuaPlanCheckRef::getPlanId, checkPlanIds));
        List<Integer> checkIds = quaPlanCheckRefs.stream().map(QuaPlanCheckRef::getCheckId).collect(Collectors.toList());
        List<QuaCheckItem> checkItemList = quaCheckItemService.listByIds(checkIds);
        List<QuaMaterialInspectionDetail> detailList = new ArrayList<>();
        checkItemList.forEach(checkItem -> {
            QuaMaterialInspectionDetail detail = new QuaMaterialInspectionDetail();
            detail.setProjectId(checkItem.getId());
            detail.setProjectCode(checkItem.getCheckItemCode());
            detail.setProjectName(checkItem.getCheckItemName());
            detail.setProjectMethod(checkItem.getCheckMethod());
            detail.setUnit(checkItem.getGoatUnit());
            detail.setTargetValue(checkItem.getGoatValue());
            detail.setIsMustCheck(0);
            detail.setNorm(checkItem.getStandardValue());

            detailList.add(detail);
        });
        dto.setQuaMaterialInspectionDetailList(detailList);
        return dto;
    }


    /**限制频繁调用*/
    public Boolean limitFrequentCalls(String redisKey, Long sec){
        if (sec == null) {
            sec = 10L;
        }
        String key = TokenUtil.getUserId() + redisKey;
        Object value = redisTemplate.opsForValue().get(key);
        if (Objects.nonNull(value)) {
            Long expire = redisTemplate.getExpire(key, TimeUnit.SECONDS);
            return true;
        }
        //同一个登录人5秒内只可以操作一次
        redisTemplate.opsForValue().set(key, "Temporarily out of sync", sec, TimeUnit.SECONDS);
        return false;
    }
}
